// SPDX-License-Identifier: GPL-2.0
/*
 * lt9211 GMSL2/GMSL1 to CSI-2 Deserializer driver
 *
 * Copyright (C) 2022 Rockchip Electronics Co., Ltd.
 *
 * V0.0X00.0X01 first version ref from max96712
 * V0.0X00.0X02 适配分辨率、支持应用层执行配置
 * V0.0X00.0X03 probe阶段稳定性提高，调整帧率
 * V0.0X00.0X04 不判断ID（默认probe OK），调整速率为450M，修改时钟为非连续时钟
 *
 */

#include <linux/string.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <linux/sysfs.h>
#include <linux/slab.h>
#include <linux/version.h>
#include <linux/compat.h>
#include <linux/rk-camera-module.h>
#include <media/media-entity.h>
#include <media/v4l2-async.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-subdev.h>
#include <linux/pinctrl/consumer.h>
#include <linux/regmap.h>
#include <linux/fpga.h>
#include "lt9211.h"

#define DRIVER_VERSION          KERNEL_VERSION(0, 0x00, 0x04)

#define DEBUG

#ifndef V4L2_CID_DIGITAL_GAIN
#define V4L2_CID_DIGITAL_GAIN       V4L2_CID_GAIN
#endif

#define LT9211_I2C_MAX_RETRY 3

#define MIN_MHZ                       100000000UL

#define LT9211_LINK_FREQ_300MHZ     300000000UL
#define LT9211_LINK_FREQ_400MHZ     400000000UL
#define LT9211_LINK_FREQ_450MHZ     450000000UL
#define LT9211_LINK_FREQ_500MHZ     500000000UL
#define LT9211_LINK_FREQ_600MHZ     600000000UL
#define LT9211_LINK_FREQ_750MHZ     750000000UL
#define LT9211_LINK_FREQ_1000MHZ   1000000000UL
#define LT9211_LINK_FREQ_1200MHZ   1200000000UL //D/CPHY 0 lt9211-a使用
#define LT9211_LINK_FREQ_1500MHZ   1500000000UL
#define LT9211_LINK_FREQ_2000MHZ   2000000000UL //DPHY 0   lt9211-c使用
#define LT9211_LINK_FREQ_2100MHZ   2100000000UL

#define USER_CMD "/bin/mipi_input_enable.elf";

static const char * const lt9211_supply_names[] = {
    "avdd",     /* Analog power */
    "dovdd",    /* Digital I/O power */
    "dvdd",     /* Digital core power */
};

#define LT9211_NUM_SUPPLIES ARRAY_SIZE(lt9211_supply_names)

struct lt9211 {
    struct i2c_client   *client;
    struct regmap *regmap;

    unsigned long link_freq_val;
    unsigned long long pixel_rate_val;

    //const s64 *link_freq_items;
    s64 link_freq_items[1];
    u32 this_i2c_addr;
    u32 who_i_am;
    u32 max_cameras_cnt;

    struct clk      *xvclk;
    struct gpio_desc    *power_gpio;
    struct gpio_desc    *reset_gpio;
    struct gpio_desc    *pwdn_gpio;
    struct regulator_bulk_data supplies[LT9211_NUM_SUPPLIES];

    struct pinctrl      *pinctrl;
    struct pinctrl_state    *pins_default;
    struct pinctrl_state    *pins_sleep;

    struct v4l2_subdev  subdev;
    struct media_pad    pad;
    struct v4l2_rect    crop;
    struct v4l2_mbus_framefmt format;
    struct v4l2_ctrl_handler ctrl_handler;
    struct v4l2_ctrl    *exposure;
    struct v4l2_ctrl    *anal_gain;
    struct v4l2_ctrl    *digi_gain;
    struct v4l2_ctrl    *hblank;
    struct v4l2_ctrl    *vblank;
    struct v4l2_ctrl    *pixel_rate;
    struct v4l2_ctrl    *link_freq;
    struct v4l2_ctrl    *test_pattern;
    struct mutex        mutex;
    bool            streaming;
    bool            power_on;
    bool            hot_plug;
    u8          is_reset;
    u32         module_index;

    const struct lt9211_mode *cur_mode;
    enum LT9211_IMAGE_MODE_INDEX image_mode;
    const char      *config_cmd_str;
    const char      *config_arg_str;

    const char      *module_facing;
    const char      *module_name;
    const char      *len_name;
};


#define to_lt9211(sd) container_of(sd, struct lt9211, subdev)


static const struct lt9211_mode supported_modes[] = {
    {
        .width = 1920,
        .height = 1080,
        .max_fps = {
            .numerator = 10000,
            //.denominator = 250000,
            .denominator = 300000,
        },
        .reg_list = lt9211_mipi_init_for_single_mode,
        //.sensor_reg_list = max96717_1080p_reglist_inside_sync,
        .sensor_reg_list = max96717_1080p_reglist_external_sync,
        .link_freq_idx = 0,
        .image_mode = IMAGE_MODE_1080P30,
        .max_cameras_cnt = 1,
    },
};

void lt9211_get_image_mode_by_index(struct lt9211 *lt9211, enum LT9211_IMAGE_MODE_INDEX index)
{
    int i;
    struct device *dev= &lt9211->client->dev;

    if(!lt9211)
    {
        return;
    }

    if(index >= IMAGE_MODE_UNKNOW)
    {
        dev_warn(dev, "Failed to get image mode, using default\n");
        lt9211->image_mode = IMAGE_MODE_1080P30;
        goto config;
    }

    for(i = 0; i < ARRAY_SIZE(supported_modes); i++)
    {
        if(supported_modes[i].image_mode == index)
        {
            lt9211->image_mode = index;
            goto config;
        }
    }

config:
    lt9211->cur_mode        = &supported_modes[lt9211->image_mode];
    lt9211->max_cameras_cnt =  supported_modes[lt9211->image_mode].max_cameras_cnt;
    lt9211->crop.width    = supported_modes[lt9211->image_mode].width;
    lt9211->crop.height   = supported_modes[lt9211->image_mode].height;

    lt9211->format.width  = lt9211->crop.width;
    lt9211->format.height = lt9211->crop.height;
    lt9211->format.field  = V4L2_FIELD_NONE;
    lt9211->format.code   = LT9211_MEDIA_BUS_FMT;;

    if(lt9211->image_mode == IMAGE_MODE_1080P30)
    {
        dev_warn(dev, "using 1920*1080 mode\n");
    }
}

static int lt9211_write_reg(struct i2c_client *client, u16 reg, u32 val)
{
    u32 buf_i, val_i;
    u8 buf[6];
    u32 len = 1;
    u8 *val_p;
    __be32 val_be;
    int retry_cnt = 0;

    dev_dbg(&client->dev, "write reg(0x%03x val:0x%02x);\n", reg, val);

    if (len > 4)
        return -EINVAL;

    buf[0] = reg >> 8;
    buf[1] = reg & 0xff;

    val_be = cpu_to_be32(val);
    val_p = (u8 *)&val_be;
    buf_i = 2;
    val_i = 4 - len;

    while (val_i < 4)
        buf[buf_i++] = val_p[val_i++];

    while(retry_cnt < I2C_RW_MAX_RETRY)
    {
        if (i2c_master_send(client, buf, len + 2) != len + 2) {
            if(reg == LT9211_REG_0)
            {
                break;
            }
            dev_warn(&client->dev, "%s: writing dev [%02x] register [0x%x] -> val [0x%02x]/[0x%02x] failed %d/%d\n",
                    __func__,  client->addr, reg, val_be, cpu_to_be32(val), retry_cnt + 1, I2C_RW_MAX_RETRY);
            retry_cnt++;
        }else
        {
            break;
        }
        msleep(40);
    }

    if(retry_cnt == I2C_RW_MAX_RETRY  && I2C_RW_MAX_RETRY != 0)
    {
        return -EIO;
    }

    return 0;
}
static const struct regmap_config lt9211_i2c_regmap = {
    .reg_bits = 8,
    .val_bits = 8,
    .max_register = 0xff,
};

__attribute__((unused)) static int lt9211_update_bits(struct lt9211 *lt9211, unsigned int reg,
                u8 mask, u8 val)
{
    int ret;
    int i;

    for(i = 0; i < LT9211_I2C_MAX_RETRY; i++)
    {
        ret = regmap_update_bits(lt9211->regmap, reg, mask, val);
        if (ret)
        {
            dev_err(&lt9211->client->dev, "update 0x%04x failed\n", reg);
            continue;
        }
        break;
    }

    return ret;
}

static int lt9211_read_reg(struct i2c_client *client, u16 reg, u32 *val)
{
    *val = i2c_smbus_read_byte_data(client, reg);
    return 0;
}


static int run_usermode_cmd(struct lt9211 *lt9211)
{
#if 1
    char cmd[32] = { 0 }; /*"/bin/mipi_input_enable.elf"*/
    char *argv[3];// = { NULL }; // = {cmd, "7","2d", NULL};
    char i2cbus[2] = { 0 };
    char devadr[4] = { 0 };
    char *envp[3] = { NULL };
    int  len;

    memcpy(cmd, lt9211->config_cmd_str, strlen(lt9211->config_cmd_str));


    // lt9211->config_arg_str = "8-002d";
    i2cbus[0] = lt9211->config_arg_str[0];
    i2cbus[1] = 0;

    len = strlen(lt9211->config_arg_str);
    if(len > 2)
    {
        devadr[0] = lt9211->config_arg_str[len - 2];
        devadr[1] = lt9211->config_arg_str[len - 1];
    }
    devadr[2] = 0;

    argv[0] = cmd;
    argv[1] = i2cbus;
    argv[2] = devadr;
    argv[3] = NULL;

    envp[0] = "HOME=/";
    envp[1] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
    envp[2] = NULL;
    /*
     * 执行的命令是：/bin/mipi_input_enable.elf  <i2c_bus> <dev_addr>
     * */
    // 对于龙讯，需要在配置结束之前就准备好MIPI PHY，因此时机只能是UMH_WAIT_EXEC
    return call_usermodehelper(cmd, argv, envp,
            UMH_WAIT_EXEC  /* wait for the exec, but not the process */);
            //UMH_WAIT_PROC /* wait for the process to complete */);
#else
    return 0;
#endif
}

static int lt9211_set_config(struct lt9211 *lt9211)
{
    // 忽略执行的返回值，以兼容执行应用程序失败的情况
    run_usermode_cmd(lt9211);
    return 0;
}


static int lt9211_get_reso_dist(const struct lt9211_mode *mode,
                 struct v4l2_mbus_framefmt *framefmt)
{
    return abs(mode->width - framefmt->width) +
        abs(mode->height - framefmt->height);
}

static const struct lt9211_mode *
lt9211_find_best_fit(struct v4l2_subdev_format *fmt)
{
    struct v4l2_mbus_framefmt *framefmt = &fmt->format;
    int dist;
    int cur_best_fit = 0;
    int cur_best_fit_dist = -1;
    unsigned int i;

    for (i = 0; i < ARRAY_SIZE(supported_modes); i++) {
        dist = lt9211_get_reso_dist(&supported_modes[i], framefmt);
        if (cur_best_fit_dist == -1 || dist < cur_best_fit_dist) {
            cur_best_fit_dist = dist;
            cur_best_fit = i;
        }
    }

    return &supported_modes[cur_best_fit];
}

static int lt9211_set_fmt(struct v4l2_subdev *sd,
               struct v4l2_subdev_pad_config *cfg,
              struct v4l2_subdev_format *fmt)
{
    struct lt9211 *lt9211 = to_lt9211(sd);
    const struct lt9211_mode *mode;

    mutex_lock(&lt9211->mutex);

    mode = lt9211_find_best_fit(fmt);
    fmt->format.code = lt9211->format.code;
    fmt->format.width = mode->width;
    fmt->format.height = mode->height;
    fmt->format.field =lt9211->format.field;
    if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
        *v4l2_subdev_get_try_format(sd, cfg, fmt->pad) = fmt->format;
#else
        mutex_unlock(&lt9211->mutex);
        return -ENOTTY;
#endif
    } else {
        if (lt9211->streaming) {
            mutex_unlock(&lt9211->mutex);
            return -EBUSY;
        }
    }

    mutex_unlock(&lt9211->mutex);

    return 0;
}

static int lt9211_get_fmt(struct v4l2_subdev *sd,
               struct v4l2_subdev_pad_config *cfg,
               struct v4l2_subdev_format *fmt)
{
    struct lt9211 *lt9211 = to_lt9211(sd);
    const struct lt9211_mode *mode = lt9211->cur_mode;

    mutex_lock(&lt9211->mutex);
    if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
        fmt->format = *v4l2_subdev_get_try_format(sd, cfg, fmt->pad);
#else
        mutex_unlock(&lt9211->mutex);
        return -ENOTTY;
#endif
    } else {
        fmt->format.width = mode->width;
        fmt->format.height = mode->height;
        fmt->format.code = LT9211_MEDIA_BUS_FMT;
        fmt->format.field = V4L2_FIELD_NONE;
    }
    mutex_unlock(&lt9211->mutex);

    return 0;
}

static int lt9211_enum_mbus_code(struct v4l2_subdev *sd,
                  struct v4l2_subdev_pad_config *cfg,
                  struct v4l2_subdev_mbus_code_enum *code)
{
    if (code->index != 0)
        return -EINVAL;
    code->code = LT9211_MEDIA_BUS_FMT;

    return 0;
}

static int lt9211_enum_frame_sizes(struct v4l2_subdev *sd,
                    struct v4l2_subdev_pad_config *cfg,
                   struct v4l2_subdev_frame_size_enum *fse)
{
    if (fse->index >= ARRAY_SIZE(supported_modes))
        return -EINVAL;

    if (fse->code != LT9211_MEDIA_BUS_FMT)
        return -EINVAL;

    fse->min_width  = supported_modes[fse->index].width;
    fse->max_width  = supported_modes[fse->index].width;
    fse->max_height = supported_modes[fse->index].height;
    fse->min_height = supported_modes[fse->index].height;

    return 0;
}

static int lt9211_g_frame_interval(struct v4l2_subdev *sd,
                    struct v4l2_subdev_frame_interval *fi)
{
    struct lt9211 *lt9211 = to_lt9211(sd);
    const struct lt9211_mode *mode = lt9211->cur_mode;

    mutex_lock(&lt9211->mutex);
    fi->interval = mode->max_fps;
    mutex_unlock(&lt9211->mutex);

    return 0;
}

static void lt9211_get_module_inf(struct lt9211 *lt9211,
                   struct rkmodule_inf *inf)
{
    memset(inf, 0, sizeof(*inf));
    strscpy(inf->base.sensor, LT9211_NAME, sizeof(inf->base.sensor));
    strscpy(inf->base.module, lt9211->module_name,
        sizeof(inf->base.module));
    strscpy(inf->base.lens, lt9211->len_name, sizeof(inf->base.lens));
}

static void lt9211_get_vicap_rst_inf(struct lt9211 *lt9211,
                   struct rkmodule_vicap_reset_info *rst_info)
{
    struct i2c_client *client = lt9211->client;

    rst_info->is_reset = lt9211->hot_plug;
    lt9211->hot_plug = false;
    rst_info->src = RKCIF_RESET_SRC_ERR_HOTPLUG;
    dev_info(&client->dev, "%s: rst_info->is_reset:%d.\n", __func__, rst_info->is_reset);
}

static void lt9211_set_vicap_rst_inf(struct lt9211 *lt9211,
                   struct rkmodule_vicap_reset_info rst_info)
{
    lt9211->is_reset = rst_info.is_reset;
}

static long lt9211_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg)
{
    struct lt9211 *lt9211 = to_lt9211(sd);
    long ret = 0;
    u32 stream = 0;

    switch (cmd) {
    case RKMODULE_GET_MODULE_INFO:
        lt9211_get_module_inf(lt9211, (struct rkmodule_inf *)arg);
        break;
    case RKMODULE_SET_QUICK_STREAM:

        stream = *((u32 *)arg);

        if (stream)
            ret = lt9211_write_reg(lt9211->client,
                 LT9211_REG_CTRL_MODE,
                 LT9211_MODE_STREAMING);
        else
            ret = lt9211_write_reg(lt9211->client,
                 LT9211_REG_CTRL_MODE,
                 LT9211_MODE_SW_STANDBY);
        break;
    case RKMODULE_GET_VICAP_RST_INFO:
        lt9211_get_vicap_rst_inf(lt9211,
            (struct rkmodule_vicap_reset_info *)arg);
        break;
    case RKMODULE_SET_VICAP_RST_INFO:
        lt9211_set_vicap_rst_inf(lt9211,
            *(struct rkmodule_vicap_reset_info *)arg);
        break;
    case RKMODULE_GET_START_STREAM_SEQ:
        // +*(int *)arg = RKMODULE_START_STREAM_FRONT;
        // *(int *)arg = RKMODULE_START_STREAM_BEHIND;
        break;
    default:
        ret = -ENOIOCTLCMD;
        break;
    }

    return ret;
}

#ifdef CONFIG_COMPAT
static long lt9211_compat_ioctl32(struct v4l2_subdev *sd,
                   unsigned int cmd, unsigned long arg)
{
    void __user *up = compat_ptr(arg);
    struct rkmodule_inf *inf;
    struct rkmodule_awb_cfg *cfg;
    struct rkmodule_vicap_reset_info *vicap_rst_inf;
    long ret = 0;
    int *seq;
    u32 stream = 0;

    switch (cmd) {
    case RKMODULE_GET_MODULE_INFO:
        inf = kzalloc(sizeof(*inf), GFP_KERNEL);
        if (!inf) {
            ret = -ENOMEM;
            return ret;
        }

        ret = lt9211_ioctl(sd, cmd, inf);
        if (!ret) {
            ret = copy_to_user(up, inf, sizeof(*inf));
            if (ret)
                ret = -EFAULT;
        }
        kfree(inf);
        break;
    case RKMODULE_AWB_CFG:
        cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
        if (!cfg) {
            ret = -ENOMEM;
            return ret;
        }

        ret = copy_from_user(cfg, up, sizeof(*cfg));
        if (!ret)
            ret = lt9211_ioctl(sd, cmd, cfg);
        else
            ret = -EFAULT;
        kfree(cfg);
        break;
    case RKMODULE_GET_VICAP_RST_INFO:
        vicap_rst_inf = kzalloc(sizeof(*vicap_rst_inf), GFP_KERNEL);
        if (!vicap_rst_inf) {
            ret = -ENOMEM;
            return ret;
        }

        ret = lt9211_ioctl(sd, cmd, vicap_rst_inf);
        if (!ret) {
            ret = copy_to_user(up, vicap_rst_inf, sizeof(*vicap_rst_inf));
            if (ret)
                ret = -EFAULT;
        }
        kfree(vicap_rst_inf);
        break;
    case RKMODULE_SET_VICAP_RST_INFO:
        vicap_rst_inf = kzalloc(sizeof(*vicap_rst_inf), GFP_KERNEL);
        if (!vicap_rst_inf) {
            ret = -ENOMEM;
            return ret;
        }

        ret = copy_from_user(vicap_rst_inf, up, sizeof(*vicap_rst_inf));
        if (!ret)
            ret = lt9211_ioctl(sd, cmd, vicap_rst_inf);
        else
            ret = -EFAULT;
        kfree(vicap_rst_inf);
        break;
    case RKMODULE_GET_START_STREAM_SEQ:
        seq = kzalloc(sizeof(*seq), GFP_KERNEL);
        if (!seq) {
            ret = -ENOMEM;
            return ret;
        }

        ret = lt9211_ioctl(sd, cmd, seq);
        if (!ret) {
            ret = copy_to_user(up, seq, sizeof(*seq));
            if (ret)
                ret = -EFAULT;
        }
        kfree(seq);
        break;
    case RKMODULE_SET_QUICK_STREAM:
        ret = copy_from_user(&stream, up, sizeof(u32));
        if (!ret)
            ret = lt9211_ioctl(sd, cmd, &stream);
        else
            ret = -EFAULT;
        break;
    default:
        ret = -ENOIOCTLCMD;
        break;
    }

    return ret;
}
#endif

static int __lt9211_start_stream(struct lt9211 *lt9211)
{
    int ret;

    ret = lt9211_set_config(lt9211);
    if (ret)
        return ret;

    /* In case these controls are set before streaming */
    mutex_unlock(&lt9211->mutex);
    ret = v4l2_ctrl_handler_setup(&lt9211->ctrl_handler);
    mutex_lock(&lt9211->mutex);
    if (ret)
        return ret;

    return 0;
}

static int __lt9211_stop_stream(struct lt9211 *lt9211)
{
    return 0;
}

static int lt9211_s_stream(struct v4l2_subdev *sd, int on)
{
    struct lt9211 *lt9211 = to_lt9211(sd);
    struct i2c_client *client = lt9211->client;
    int ret = 0;

    dev_info(&client->dev, "%s: on: %d, %dx%d@%d\n", __func__, on,
                lt9211->cur_mode->width,
                lt9211->cur_mode->height,
        DIV_ROUND_CLOSEST(lt9211->cur_mode->max_fps.denominator,
                  lt9211->cur_mode->max_fps.numerator));

    mutex_lock(&lt9211->mutex);
    on = !!on;
    if (on == lt9211->streaming)
        goto unlock_and_return;

    if (on) {
        ret = pm_runtime_get_sync(&client->dev);
        if (ret < 0) {
            pm_runtime_put_noidle(&client->dev);
            goto unlock_and_return;
        }

        ret = __lt9211_start_stream(lt9211);
        if (ret) {
            v4l2_err(sd, "start stream failed while write regs\n");
            pm_runtime_put(&client->dev);
            goto unlock_and_return;
        }
    } else {
        __lt9211_stop_stream(lt9211);
        pm_runtime_put(&client->dev);
    }

    lt9211->streaming = on;

unlock_and_return:
    mutex_unlock(&lt9211->mutex);

    return ret;
}

static int lt9211_s_power(struct v4l2_subdev *sd, int on)
{
    struct lt9211 *lt9211 = to_lt9211(sd);
    struct i2c_client *client = lt9211->client;
    int ret = 0;

    mutex_lock(&lt9211->mutex);

    /* If the power state is not modified - no work to do. */
    if (lt9211->power_on == !!on)
        goto unlock_and_return;

    if (on) {
        ret = pm_runtime_get_sync(&client->dev);
        if (ret < 0) {
            pm_runtime_put_noidle(&client->dev);
            goto unlock_and_return;
        }

        lt9211->power_on = true;
    } else {
        pm_runtime_put(&client->dev);
        lt9211->power_on = false;
    }

unlock_and_return:
    mutex_unlock(&lt9211->mutex);

    return ret;
}

/* Calculate the delay in us by clock rate and clock cycles */
static inline u32 lt9211_cal_delay(u32 cycles)
{
    return DIV_ROUND_UP(cycles, LT9211_XVCLK_FREQ / 1000 / 1000);
}

static int __lt9211_power_on(struct lt9211 *lt9211)
{
    int ret;
    u32 delay_us;
    struct device *dev = &lt9211->client->dev;

    if (!IS_ERR(lt9211->power_gpio))
        gpiod_set_value_cansleep(lt9211->power_gpio, 1);

    usleep_range(1000, 2000);

    if (!IS_ERR_OR_NULL(lt9211->pins_default)) {
        ret = pinctrl_select_state(lt9211->pinctrl,
                       lt9211->pins_default);
        if (ret < 0)
            dev_err(dev, "could not set pins\n");
    }

    if (!IS_ERR(lt9211->reset_gpio))
        gpiod_set_value_cansleep(lt9211->reset_gpio, 0);

    ret = regulator_bulk_enable(LT9211_NUM_SUPPLIES, lt9211->supplies);
    if (ret < 0) {
        dev_err(dev, "Failed to enable regulators\n");
        goto disable_clk;
    }

    if (!IS_ERR(lt9211->reset_gpio))
        gpiod_set_value_cansleep(lt9211->reset_gpio, 1);

    usleep_range(500, 1000);
    if (!IS_ERR(lt9211->pwdn_gpio))
        gpiod_set_value_cansleep(lt9211->pwdn_gpio, 1);

    /* 8192 cycles prior to first SCCB transaction */
    delay_us = lt9211_cal_delay(8192);
    usleep_range(delay_us, delay_us * 2);

    return 0;

disable_clk:
    clk_disable_unprepare(lt9211->xvclk);

    return ret;
}

static void __lt9211_power_off(struct lt9211 *lt9211)
{
    int ret;
    struct device *dev = &lt9211->client->dev;

    if (!IS_ERR(lt9211->pwdn_gpio))
        gpiod_set_value_cansleep(lt9211->pwdn_gpio, 0);
    clk_disable_unprepare(lt9211->xvclk);
    if (!IS_ERR(lt9211->reset_gpio))
        gpiod_set_value_cansleep(lt9211->reset_gpio, 0);

    if (!IS_ERR_OR_NULL(lt9211->pins_sleep)) {
        ret = pinctrl_select_state(lt9211->pinctrl,
                       lt9211->pins_sleep);
        if (ret < 0)
            dev_dbg(dev, "could not set pins\n");
    }
    if (!IS_ERR(lt9211->power_gpio))
        gpiod_set_value_cansleep(lt9211->power_gpio, 0);

    regulator_bulk_disable(LT9211_NUM_SUPPLIES, lt9211->supplies);
}

static int lt9211_runtime_resume(struct device *dev)
{
    struct i2c_client *client = to_i2c_client(dev);
    struct v4l2_subdev *sd = i2c_get_clientdata(client);
    struct lt9211 *lt9211 = to_lt9211(sd);

    return __lt9211_power_on(lt9211);
}

static int lt9211_runtime_suspend(struct device *dev)
{
    struct i2c_client *client = to_i2c_client(dev);
    struct v4l2_subdev *sd = i2c_get_clientdata(client);
    struct lt9211 *lt9211 = to_lt9211(sd);

    __lt9211_power_off(lt9211);

    return 0;
}

#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
static int lt9211_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
{
    struct lt9211 *lt9211 = to_lt9211(sd);
    struct v4l2_mbus_framefmt *try_fmt =
                v4l2_subdev_get_try_format(sd, fh->pad, 0);
    const struct lt9211_mode *def_mode = &supported_modes[0];

    mutex_lock(&lt9211->mutex);
    /* Initialize try_fmt */
    try_fmt->width = def_mode->width;
    try_fmt->height = def_mode->height;
    try_fmt->code = LT9211_MEDIA_BUS_FMT;
    try_fmt->field = V4L2_FIELD_NONE;

    mutex_unlock(&lt9211->mutex);
    /* No crop or compose */

    return 0;
}
#endif

static int lt9211_enum_frame_interval(struct v4l2_subdev *sd,
                       struct v4l2_subdev_pad_config *cfg,
                       struct v4l2_subdev_frame_interval_enum *fie)
{
    if (fie->index >= ARRAY_SIZE(supported_modes))
        return -EINVAL;

    if (fie->code != LT9211_MEDIA_BUS_FMT)
        return -EINVAL;

    fie->width = supported_modes[fie->index].width;
    fie->height = supported_modes[fie->index].height;
    fie->interval = supported_modes[fie->index].max_fps;

    return 0;
}

static int lt9211_g_mbus_config(struct v4l2_subdev *sd, unsigned int pad,
                struct v4l2_mbus_config *config)
{
    config->type = V4L2_MBUS_CSI2_DPHY;
    config->flags = V4L2_MBUS_CSI2_4_LANE |
            V4L2_MBUS_CSI2_CHANNEL_0; // | V4L2_MBUS_CSI2_CONTINUOUS_CLOCK;
    return 0;
}

static int lt9211_get_selection(struct v4l2_subdev *sd,
                struct v4l2_subdev_pad_config *cfg,
                struct v4l2_subdev_selection *sel)
{
    struct lt9211 *lt9211 = to_lt9211(sd);

    if (sel->target == V4L2_SEL_TGT_CROP_BOUNDS) {
        sel->r.left = 0;
        sel->r.width = lt9211->cur_mode->width;
        sel->r.top = 0;
        sel->r.height = lt9211->cur_mode->height;
        return 0;
    }

    return -EINVAL;
}

/**
 * Helper function to change binning and set both compose and format.
 *
 * We have two entry points to change binning: set_fmt and
 * set_selection(COMPOSE). Both have to compute the new output size
 * and set it in both the compose rect and the frame format size. We
 * also need to do the same things after setting cropping to restore
 * 1:1 binning.
 *
 * This function contains the common code for these three cases, it
 * has many arguments in order to accommodate the needs of all of
 * them.
 *
 * Must be called with lt9211->lock locked.
 *
 * @lt9211: The device object
 * @cfg:    The pad config we are editing for TRY requests
 * @which:  V4L2_SUBDEV_FORMAT_ACTIVE or V4L2_SUBDEV_FORMAT_TRY from the caller
 * @width:  Input-output parameter: set to the desired width before
 *          the call, contains the chosen value after returning successfully
 * @height: Input-output parameter for height (see @width)
 * @flags:  Selection flags from struct v4l2_subdev_selection, or 0 if not
 *          available (when called from set_fmt)
 */
static int __lt9211_change_compose(struct lt9211 *lt9211,
                   struct v4l2_subdev_pad_config *cfg,
                   u32 which,
                   u32 *width,
                   u32 *height,
                   u32 flags)
{
#if 1
    //struct device *dev = &lt9211->client->dev;
    const struct v4l2_rect *cur_crop;
    struct v4l2_mbus_framefmt *tgt_fmt;
    //unsigned int i;
    //const struct lt9211_mode *best_mode = lt9211->cur_mode;

    if (which == V4L2_SUBDEV_FORMAT_TRY) {
        cur_crop = &cfg->try_crop;
        tgt_fmt = &cfg->try_fmt;
    } else {
        cur_crop = &lt9211->crop;
        tgt_fmt = &lt9211->format;
    }


    *width = cur_crop->width;
    *height = cur_crop->height;

    //if (which == V4L2_SUBDEV_FORMAT_ACTIVE)
    //    lt9211->mode = best_mode;

    tgt_fmt->width = *width;
    tgt_fmt->height = *height;
    tgt_fmt->field = V4L2_FIELD_NONE;

#endif
    return 0;
}

#define LT9211_ROUND(dim, step, flags)            \
    ((flags) & V4L2_SEL_FLAG_GE         \
     ? roundup((dim), (step))           \
     : ((flags) & V4L2_SEL_FLAG_LE          \
        ? rounddown((dim), (step))          \
        : rounddown((dim) + (step) / 2, (step))))

static int lt9211_set_selection_crop(struct lt9211 *lt9211,
                     struct v4l2_subdev_pad_config *cfg,
                     struct v4l2_subdev_selection *sel)
{
    struct v4l2_rect *tgt_crop;
    struct v4l2_rect new_crop;
    bool size_changed;

    /*
     * h_step could be 12 or 24 depending on the binning. But we
     * won't know the binning until we choose the mode later in
     * __lt9211_change_compose(). Thus let's be safe and use the
     * most conservative value in all cases.
     */
    const u32 h_step = 24;

    new_crop.width = min_t(u32,
                   LT9211_ROUND(sel->r.width, h_step, sel->flags),
                   lt9211->cur_mode->width);

    /* Constraint: HTRIMMING_END - HTRIMMING_START >= 144 */
    if (new_crop.width < 1920)
        new_crop.width = 1920;

    new_crop.left = min_t(u32,
                  LT9211_ROUND(sel->r.left, h_step, 0),
                  lt9211->cur_mode->width - new_crop.width);

    new_crop.height = min_t(u32,
                LT9211_ROUND(sel->r.height, 2, sel->flags),
                lt9211->cur_mode->height);

    new_crop.top = min_t(u32, LT9211_ROUND(sel->r.top, 2, 0),
                 lt9211->cur_mode->height - new_crop.height);

    sel->r = new_crop;

    if (sel->which == V4L2_SUBDEV_FORMAT_TRY)
        tgt_crop = &cfg->try_crop;
    else
        tgt_crop = &lt9211->crop;

    mutex_lock(&lt9211->mutex);

    size_changed = (new_crop.width != tgt_crop->width ||
            new_crop.height != tgt_crop->height);

    /* __lt9211_change_compose needs the new size in *tgt_crop */
    *tgt_crop = new_crop;

    /* if crop size changed then reset the output image size */
    if (size_changed)
        __lt9211_change_compose(lt9211, cfg, sel->which,
                    &new_crop.width, &new_crop.height,
                    sel->flags);

    mutex_unlock(&lt9211->mutex);

    return 0;
}

static int lt9211_set_selection(struct v4l2_subdev *sd,
                struct v4l2_subdev_pad_config *cfg,
                struct v4l2_subdev_selection *sel)
{
    struct lt9211 *lt9211 = to_lt9211(sd);

    if (sel->pad != 0)
        return -EINVAL;

    if (sel->target == V4L2_SEL_TGT_CROP)
        return lt9211_set_selection_crop(lt9211, cfg, sel);

    return -EINVAL;
}

static const struct dev_pm_ops lt9211_pm_ops = {
    SET_RUNTIME_PM_OPS(lt9211_runtime_suspend,
               lt9211_runtime_resume, NULL)
};

#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
static const struct v4l2_subdev_internal_ops lt9211_internal_ops = {
    .open = lt9211_open,
};
#endif

static const struct v4l2_subdev_core_ops lt9211_core_ops = {
    .s_power = lt9211_s_power,
    .ioctl = lt9211_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl32 = lt9211_compat_ioctl32,
#endif
};

static const struct v4l2_subdev_video_ops lt9211_video_ops = {
    .s_stream = lt9211_s_stream,
    .g_frame_interval = lt9211_g_frame_interval,
};

static const struct v4l2_subdev_pad_ops lt9211_pad_ops = {
    .enum_mbus_code = lt9211_enum_mbus_code,
    .enum_frame_size = lt9211_enum_frame_sizes,
    .enum_frame_interval = lt9211_enum_frame_interval,
    .get_fmt = lt9211_get_fmt,
    .set_fmt = lt9211_set_fmt,
    .get_selection = lt9211_get_selection,
    .set_selection = lt9211_set_selection,
    .get_mbus_config = lt9211_g_mbus_config,
};

static const struct v4l2_subdev_ops lt9211_subdev_ops = {
    .core   = &lt9211_core_ops,
    .video  = &lt9211_video_ops,
    .pad    = &lt9211_pad_ops,
};

static int lt9211_initialize_controls(struct lt9211 *lt9211)
{
    const struct lt9211_mode *mode;
    struct v4l2_ctrl_handler *handler;
    unsigned long long pixel_rate_val;
    int ret;

    handler = &lt9211->ctrl_handler;
    mode = lt9211->cur_mode;
    ret = v4l2_ctrl_handler_init(handler, 2);
    if (ret)
        return ret;
    handler->lock = &lt9211->mutex;

    lt9211->link_freq = v4l2_ctrl_new_int_menu(handler, NULL,
            V4L2_CID_LINK_FREQ,
            1, 0, lt9211->link_freq_items);

    pixel_rate_val = lt9211->pixel_rate_val;
    lt9211->pixel_rate = v4l2_ctrl_new_std(handler, NULL,
            V4L2_CID_PIXEL_RATE,
            0, pixel_rate_val,
            1, pixel_rate_val);

    __v4l2_ctrl_s_ctrl(lt9211->link_freq,
               mode->link_freq_idx);

    if (handler->error) {
        ret = handler->error;
        dev_err(&lt9211->client->dev,
            "Failed to init controls(%d)\n", ret);
        goto err_free_handler;
    }

    lt9211->subdev.ctrl_handler = handler;

    return 0;

err_free_handler:
    v4l2_ctrl_handler_free(handler);

    return ret;
}

static int lt9211_check_sensor_id(struct lt9211 *lt9211,
                   struct i2c_client *client)
{
#if 1
    struct device *dev = &lt9211->client->dev;
    u32 id_val[LT9211_REG_CHIP_ID_SIZE] = {0};
    u32 id_should[LT9211_REG_CHIP_ID_SIZE] = {
        CHIP_ID_1,
        CHIP_ID_2,
        CHIP_ID_3
    };
    u32 reg[LT9211_REG_CHIP_ID_SIZE] = {
        LT9211_REG_CHIP_ID_1,
        LT9211_REG_CHIP_ID_2,
        LT9211_REG_CHIP_ID_3
    };
    int ret, i, err_cnt;
    int retry_cnt;
    for(retry_cnt = 0; retry_cnt < LT9211_I2C_MAX_RETRY; retry_cnt++)
    {
        msleep(40);
        // switch page
        ret = lt9211_update_bits(lt9211, 0xff, 0xff, 0x81);
        if (ret) {
            dev_err(dev, "Switch error\n");
            //continue;
        }
        err_cnt = 0;
        for (i = 0; i < LT9211_REG_CHIP_ID_SIZE; i++)
        {
            ret = lt9211_read_reg(client, reg[i],  &id_val[i]);
            if (id_val[i] != id_should[i]) {
                err_cnt++;
                dev_err(dev, "Unexpected sensor id(%02x)/(%02x), ret(%d)\n", id_val[i], id_should[i], ret);
                //goto err;
            }
        }
        // 判断不要太严格
        if(err_cnt == LT9211_REG_CHIP_ID_SIZE) //err_cnt > 0)
        {
            dev_err(dev, "mismatch\n");
            //continue;
        }else
        {
            goto ok;
        }
        goto ok;
    }
//err:
    return -ENODEV;

ok:
    dev_info(dev, "Detected\n");

#endif
    return 0;
}

static int lt9211_configure_regulators(struct lt9211 *lt9211)
{
    unsigned int i;

    for (i = 0; i < LT9211_NUM_SUPPLIES; i++)
        lt9211->supplies[i].supply = lt9211_supply_names[i];

    return devm_regulator_bulk_get(&lt9211->client->dev,
                    LT9211_NUM_SUPPLIES,
                    lt9211->supplies);
}

static int lt9211_probe(struct i2c_client *client,
             const struct i2c_device_id *id)
{
    struct device *dev = &client->dev;
    struct device_node *node = dev->of_node;
    struct lt9211 *lt9211;
    struct v4l2_subdev *sd;
    //enum LT9211_IMAGE_MODE_INDEX image_mode = IMAGE_MODE_UNKNOW;
    char facing[2];
    u32 tmp = 0;
    int ret;

    dev_info(dev, "driver version: %02x.%02x.%02x",
        DRIVER_VERSION >> 16,
        (DRIVER_VERSION & 0xff00) >> 8,
        DRIVER_VERSION & 0x00ff);

    lt9211 = devm_kzalloc(dev, sizeof(*lt9211), GFP_KERNEL);
    if (!lt9211)
        return -ENOMEM;

    lt9211->regmap = devm_regmap_init_i2c(client, &lt9211_i2c_regmap);
    if (IS_ERR(lt9211->regmap))
    {
        dev_info(dev, "devm_regmap_init_i2c error\n");
        return PTR_ERR(lt9211->regmap);
    }

    ret = of_property_read_u32(node, RKMODULE_CAMERA_MODULE_INDEX,
                   &lt9211->module_index);
    ret |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_FACING,
                       &lt9211->module_facing);
    ret |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_NAME,
                       &lt9211->module_name);
    ret |= of_property_read_string(node, RKMODULE_CAMERA_LENS_NAME,
                       &lt9211->len_name);
    if (ret) {
        dev_err(dev, "could not get module information!\n");
        return -EINVAL;
    }

    lt9211->client = client;
    // 涉及到寄存器地址的切换
    lt9211->this_i2c_addr = client->addr;

    ret = of_property_read_u32(node, "schips,index", &lt9211->who_i_am);
    if (ret) {
        dev_err(dev, "could not get device index!\n");
        //return -EINVAL;
    }
#if 0

    /* 为了区分多个总线上的从设备地址 */
    if(INDEX_LT9211_A == lt9211->who_i_am)
    {
        lt9211->cameras_i2c_addr[0] = ADDR_SERIALIZER_0;
        lt9211->cameras_i2c_addr[1] = ADDR_SERIALIZER_1;
        lt9211->cameras_i2c_addr[2] = ADDR_SERIALIZER_2;
        lt9211->cameras_i2c_addr[3] = ADDR_SERIALIZER_3;
    }else
    {
        lt9211->cameras_i2c_addr[0] = ADDR_SERIALIZER_4;
        lt9211->cameras_i2c_addr[1] = ADDR_SERIALIZER_5;
        lt9211->cameras_i2c_addr[2] = ADDR_SERIALIZER_6;
        lt9211->cameras_i2c_addr[3] = ADDR_SERIALIZER_7;
    }
#endif

#define MIPI_FREQ		371250000
    /* 指定速率 */
    lt9211->link_freq_val = LT9211_LINK_FREQ_450MHZ;//MIPI_FREQ; // 0/1
    lt9211->link_freq_items[0] = lt9211->link_freq_val;
    if(INDEX_MAX96712_C == lt9211->who_i_am)
    {
        lt9211->link_freq_val = LT9211_LINK_FREQ_450MHZ; // 2/3
        //lt9211->link_freq_val = LT9211_LINK_FREQ_300MHZ;
        lt9211->link_freq_items[0] = lt9211->link_freq_val;
    }

    tmp = 0;
    ret = of_property_read_u32(node, "force_rate", &tmp);
    dev_err(dev, "%s: Force data rate get %d/%d!\n", __func__, ret, tmp);
    if (ret == 0) {
        if(tmp >= MIN_MHZ)
        {
            dev_info(dev, "%s: Force data rate <%u>!\n", __func__, tmp);
            lt9211->link_freq_val = tmp;
            lt9211->link_freq_items[0] = lt9211->link_freq_val;
        }else
        {
            dev_err(dev, "%s: Force data rate should bigger than  <%lu>/<%u>!\n", __func__, MIN_MHZ, tmp);
        }
    }
    /* pixel rate = link frequency * 2 * lanes / BITS_PER_SAMPLE */
    lt9211->pixel_rate_val = (lt9211->link_freq_val) * 2LL * (LT9211_LANES) / (LT9211_BITS_PER_SAMPLE);

    dev_info(&client->dev, "%s: Set Data Rate  : <%lu>\n",  __func__, lt9211->link_freq_val);
    //dev_info(&client->dev, "%s: Set Pixel Rate : <%llu>\n",  __func__, lt9211->pixel_rate_val);

    // 分辨率模式
    lt9211->image_mode = IMAGE_MODE_1080P30;
    lt9211_get_image_mode_by_index(lt9211, IMAGE_MODE_1080P30);


    // 获取配置命令，依赖于应用层(可选的)
    // 1.cmd
    lt9211->config_cmd_str = NULL;
    ret = of_property_read_string(node, "px0703,config_cmd", &lt9211->config_cmd_str);
    if (ret) {
        dev_err(dev, "%s: Failed to get config !\n", __func__);
        lt9211->config_cmd_str = USER_CMD;
    }
    // 2.arg
    lt9211->config_arg_str = NULL;
#if 1
    ret = of_property_read_string(node, "px0703,config_arg", &lt9211->config_arg_str);
    if (ret) {
        dev_err(dev, "%s: Failed to get config id !\n", __func__);
        lt9211->config_arg_str = dev_name(dev);
    }
#else
    lt9211->config_arg_str = dev_name(dev);
#endif


    /* 配置电源控制（基于GPIO）*/
    lt9211->power_gpio = devm_gpiod_get(dev, "power", GPIOD_OUT_LOW);
    if (IS_ERR(lt9211->power_gpio))
    {
        //dev_warn(dev, "Failed to get power-gpios, maybe no use\n");
    }

    lt9211->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
    if (IS_ERR(lt9211->reset_gpio))
    {
        //dev_warn(dev, "Failed to get reset-gpios\n");
    }

    lt9211->pwdn_gpio = devm_gpiod_get(dev, "pwdn", GPIOD_OUT_LOW);
    if (IS_ERR(lt9211->pwdn_gpio))
    {
        //dev_warn(dev, "Failed to get pwdn-gpios\n");
    }

    ret = lt9211_configure_regulators(lt9211);
    if (ret) {
        dev_err(dev, "Failed to get power regulators\n");
        return ret;
    }

    lt9211->pinctrl = devm_pinctrl_get(dev);
    if (!IS_ERR(lt9211->pinctrl)) {
        lt9211->pins_default =
            pinctrl_lookup_state(lt9211->pinctrl,
                         OF_CAMERA_PINCTRL_STATE_DEFAULT);
        if (IS_ERR(lt9211->pins_default))
            dev_err(dev, "could not get default pinstate\n");

        lt9211->pins_sleep =
            pinctrl_lookup_state(lt9211->pinctrl,
                         OF_CAMERA_PINCTRL_STATE_SLEEP);
        if (IS_ERR(lt9211->pins_sleep))
            dev_err(dev, "could not get sleep pinstate\n");
    }

    mutex_init(&lt9211->mutex);

    sd = &lt9211->subdev;
    v4l2_i2c_subdev_init(sd, client, &lt9211_subdev_ops);
    ret = lt9211_initialize_controls(lt9211);
    if (ret)
        goto err_destroy_mutex;

    ret = __lt9211_power_on(lt9211);
    if (ret)
        goto err_free_handler;

    ret = lt9211_check_sensor_id(lt9211, client);
    if (ret)
        goto err_power_off;

#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
    sd->internal_ops = &lt9211_internal_ops;
    sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
#endif
#if defined(CONFIG_MEDIA_CONTROLLER)
    lt9211->pad.flags = MEDIA_PAD_FL_SOURCE;
    sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
    ret = media_entity_pads_init(&sd->entity, 1, &lt9211->pad);
    if (ret < 0)
        goto err_power_off;
#endif

    memset(facing, 0, sizeof(facing));
    if (strcmp(lt9211->module_facing, "back") == 0)
        facing[0] = 'b';
    else
        facing[0] = 'f';

    snprintf(sd->name, sizeof(sd->name), "m%02d_%s_%s %s",
         lt9211->module_index, facing,
         LT9211_NAME, dev_name(sd->dev));
    ret = v4l2_async_register_subdev_sensor_common(sd);
    if (ret) {
        dev_err(dev, "v4l2 async register subdev failed\n");
        goto err_clean_entity;
    }

    pm_runtime_set_active(dev);
    pm_runtime_enable(dev);
    pm_runtime_idle(dev);

    dev_info(dev, "I'm [%s] in [%s]\n", dev_driver_string(dev), dev_name(dev));
    //lt9211_enable_cameras_link(lt9211, 0);
    return 0;

err_clean_entity:
#if defined(CONFIG_MEDIA_CONTROLLER)
    media_entity_cleanup(&sd->entity);
#endif
err_power_off:
    __lt9211_power_off(lt9211);
err_free_handler:
    v4l2_ctrl_handler_free(&lt9211->ctrl_handler);
err_destroy_mutex:
    mutex_destroy(&lt9211->mutex);

    return ret;
}

static int lt9211_remove(struct i2c_client *client)
{
    struct v4l2_subdev *sd = i2c_get_clientdata(client);
    struct lt9211 *lt9211 = to_lt9211(sd);

    v4l2_async_unregister_subdev(sd);
#if defined(CONFIG_MEDIA_CONTROLLER)
    media_entity_cleanup(&sd->entity);
#endif
    v4l2_ctrl_handler_free(&lt9211->ctrl_handler);
    mutex_destroy(&lt9211->mutex);

    pm_runtime_disable(&client->dev);
    if (!pm_runtime_status_suspended(&client->dev))
        __lt9211_power_off(lt9211);
    pm_runtime_set_suspended(&client->dev);

    return 0;
}

#if IS_ENABLED(CONFIG_OF)
static const struct of_device_id lt9211_of_match[] = {
    { .compatible = "lontium,lt9211" },
    {},
};
MODULE_DEVICE_TABLE(of, lt9211_of_match);
#endif

static const struct i2c_device_id lt9211_match_id[] = {
    { "lontium,lt9211", 0 },
    {},
};

static struct i2c_driver lt9211_i2c_driver = {
    .driver = {
        .name = LT9211_NAME,
        .pm = &lt9211_pm_ops,
        .of_match_table = of_match_ptr(lt9211_of_match),
    },
    .probe      = &lt9211_probe,
    .remove     = &lt9211_remove,
    .id_table   = lt9211_match_id,
};

static int __init sensor_mod_init(void)
{
    return i2c_add_driver(&lt9211_i2c_driver);
}

static void __exit sensor_mod_exit(void)
{
    i2c_del_driver(&lt9211_i2c_driver);
}

device_initcall_sync(sensor_mod_init);
module_exit(sensor_mod_exit);

MODULE_DESCRIPTION("lt9211 sensor driver");
MODULE_LICENSE("GPL");

